#include "utility.h"
#include <cmath>


namespace qtangram {
    QPolygonF enlargePolygon(const QPolygonF& poly, double margin) {
	QPolygonF enlarged;
	double distance;
	QPointF middle = geometricCenter(poly);
	double k;
	QPointF point;
	for(auto it = poly.begin(); it != poly.end(); ++it) {
	    point = *it;
	    point -= middle;
	    distance = sqrt( pow(point.x(),2) + pow(point.y(),2) );
	    k = (distance + margin) / distance;
	    point *= k;
	    point += middle;
	    enlarged << point;
	}
	return enlarged;
    }

    QPointF geometricCenter(const QPolygonF& poly) {
	double x = 0., y = 0.;
	for(auto it = poly.begin(); it != poly.end(); ++it) {
	    x += (*it).x();
	    y += (*it).y();
	}
	return QPointF(x/poly.size(),y/poly.size());
    }

    bool connected(QPolygonF first, QPolygonF second) {
	QPolygonF tmp;
	double sc = 1.1;
	QPolygonF s = scaleInPlace(second, sc);
	tmp = s.intersected(first);
	return !(tmp.isEmpty());
    }

    QPolygonF scale(const QPolygonF& poly, double sc) {
	QPolygonF scaled;
	for(auto it = poly.begin(); it != poly.end(); ++it)
	    scaled << *it * sc;
	return scaled;
    }

    QPolygonF scaleInPlace(QPolygonF poly, double sc) {
	QPolygonF scaled = poly;
	QPointF ref = geometricCenter(poly);
	for(auto it = scaled.begin(); it != scaled.end(); ++it) {
	    *it -= ref; // moving to the center of coord. system
	    *it *= sc; // scaling
	    *it += ref; //moving back
	}
	return scaled;
    }

    QPolygonF summEnlargedPolygons(const QLinkedList<Tan>& tans, double margin) throw(std::runtime_error) {
	try {
	    std::list<QPolygonF> unbounded;
	    QPolygonF tmp, result;
	    for(auto it = tans.begin(); it != tans.end(); ++it) {
		unbounded.push_back((*it).polyCurrent);
	    }
	    if(unbounded.empty()) throw std::runtime_error("Nothing to unite. Empty tans' list provided.");
	    tmp = unbounded.front();
	    result = enlargePolygon(tmp,margin);
	    unbounded.pop_front();
	    size_t before;
	    while((before = unbounded.size())) {
		for(auto it = unbounded.begin(); it != unbounded.end(); ++it) {
		    if(connected(tmp, *it)) {
			tmp = tmp.united(*it);
			result = result.united(enlargePolygon(*it,margin));
			unbounded.erase(it);
			break;
		    }
		}
		if(!unbounded.empty() && unbounded.size() == before) {
		    throw std::runtime_error("One (or more) tan is detached.");
		}
	    }
	    return result;
	}
	catch(const std::runtime_error& e) {
	    std::cerr << e.what();
	    throw;
	}
    }

    double distance(const QPointF & p1, const QPointF & p2) {
	double xr (p2.x() - p1.x());
	double yr (p2.y() - p1.y());
	return sqrt(xr*xr+yr*yr);
    }

    Tan* findClosest(Tan &ref_tan, QLinkedList<Tan*> &tans) {
	QMultiMap<double, Tan*> distances; // distance, Tan
	QPolygonF & ref_poly = ref_tan.polyCurrent;
	// for each reference point
	for (auto ref = ref_poly.begin(); ref != ref_poly.end(); ++ref) {
	    // count distance to the rest of points
	    for (auto it = tans.begin(); it != tans.end(); ++it) {
		QPolygonF & poly = (*it)->polyCurrent;
		for (auto target = poly.begin(); target != poly.end(); ++target)
		    distances.insert(distance(*ref, *target), *it);
	    }
	}
	return (*distances.begin());
    }
}